﻿using System;
using System.Collections.Generic;
using System.Text;

namespace RozpoznawanieLiter
{
    class Sieć
    {
        private int[] warstwyINeurony;
        private double wspolczynnikUczenia;
        private double momentum;

        private double[][] wyjscia;
        private double[][][] wagi;
        private double[][] bledy;
        private double[][][] poprzednieWagi;

        private Random r = new Random();

        public Sieć(int[] win, double wu, double m)
        {
            this.warstwyINeurony = win;
            this.wspolczynnikUczenia = wu;
            this.momentum = m;

            wyjscia = new double[warstwyINeurony.Length][];
            for (int i = 0; i < warstwyINeurony.Length; i++)
                wyjscia[i] = new double[warstwyINeurony[i]];

            bledy = new double[warstwyINeurony.Length][];
            for (int i = 1; i < warstwyINeurony.Length; i++)
                bledy[i] = new double[warstwyINeurony[i]];

            wagi = new double[warstwyINeurony.Length][][];
            for (int i = 1; i < warstwyINeurony.Length; i++)
                wagi[i] = new double[warstwyINeurony[i]][];
            for (int i = 1; i < warstwyINeurony.Length; i++)
            {
                for (int j = 0; j < warstwyINeurony[i]; j++)
                    wagi[i][j] = new double[warstwyINeurony[i - 1] + 1];
            }

            poprzednieWagi = new double[warstwyINeurony.Length][][];
            for (int i = 1; i < warstwyINeurony.Length; i++)
                poprzednieWagi[i] = new double[warstwyINeurony[i]][];
            for (int i = 1; i < warstwyINeurony.Length; i++)
            {
                for (int j = 0; j < warstwyINeurony[i]; j++)
                    poprzednieWagi[i][j] = new double[warstwyINeurony[i - 1] + 1];
            }

            for (int i = 1; i < warstwyINeurony.Length; i++)
            {
                for (int j = 0; j < warstwyINeurony[i]; j++)
                {
                    for (int k = 0; k < wagi[i][j].Length; k++)
                        wagi[i][j][k] = ((double)(r.Next((int)(-1 * 5), (int)(1 * 5)))) / 10;
                }
            }

            for (int i = 1; i < warstwyINeurony.Length; i++)
            {
                for (int j = 0; j < warstwyINeurony[i]; j++)
                {
                    for (int k = 0; k < poprzednieWagi[i][j].Length; k++)
                        poprzednieWagi[i][j][k] = 0.0;
                }
            }
        }

        private double sigmoid(double x)
        {
            return (double)(1 / (1 + Math.Exp(-5*x)));
        }

        public void wyliczanie(double[] we)
        {
            double pobudzenie;

            for (int i = 0; i < warstwyINeurony[0]; i++)
                wyjscia[0][i] = we[i];

            for (int i = 1; i < warstwyINeurony.Length; i++)
            {
                for (int j = 0; j < warstwyINeurony[i]; j++)
                {
                    pobudzenie = 0.0;

                    for (int k = 0; k < warstwyINeurony[i - 1]; k++)
                        pobudzenie += wyjscia[i - 1][k] * wagi[i][j][k];

                    pobudzenie += wagi[i][j][warstwyINeurony[i - 1]];

                    wyjscia[i][j] = sigmoid(pobudzenie);
                }
            }
        }

        public void propagacja(double[] we, double[] oczekiwaneWyjscia)
        {
            double w;

            wyliczanie(we);

            for (int i = 0; i < warstwyINeurony[warstwyINeurony.Length - 1]; i++)
                bledy[warstwyINeurony.Length - 1][i] = 5 * wyjscia[warstwyINeurony.Length - 1][i] * (1 - wyjscia[warstwyINeurony.Length - 1][i]) * (oczekiwaneWyjscia[i] - wyjscia[warstwyINeurony.Length - 1][i]);

            for (int i = warstwyINeurony.Length - 2; i > 0; i--)
            {
                for (int j = 0; j < warstwyINeurony[i]; j++)
                {
                    w = 0.0;

                    for (int k = 0; k < warstwyINeurony[i + 1]; k++)
                        w += bledy[i + 1][k] * wagi[i + 1][k][j];

                    bledy[i][j] = 5 * wyjscia[i][j] * (1 - wyjscia[i][j]) * w;
                }
            }

            for (int i = 1; i < warstwyINeurony.Length; i++)
            {
                for (int j = 0; j < warstwyINeurony[i]; j++)
                {
                    for (int k = 0; k < warstwyINeurony[i - 1]; k++)
                        wagi[i][j][k] += momentum * poprzednieWagi[i][j][k];

                    wagi[i][j][warstwyINeurony[i - 1]] += momentum * poprzednieWagi[i][j][warstwyINeurony[i - 1]];
                }
            }

            for (int i = 1; i < warstwyINeurony.Length; i++)
            {
                for (int j = 0; j < warstwyINeurony[i]; j++)
                {
                    for (int k = 0; k < warstwyINeurony[i - 1]; k++)
                    {
                        poprzednieWagi[i][j][k] = wspolczynnikUczenia * bledy[i][j] * wyjscia[i - 1][k];
                        wagi[i][j][k] += poprzednieWagi[i][j][k];
                    }

                    poprzednieWagi[i][j][warstwyINeurony[i - 1]] = wspolczynnikUczenia * bledy[i][j];
                    wagi[i][j][warstwyINeurony[i - 1]] += poprzednieWagi[i][j][warstwyINeurony[i - 1]];
                }
            }
        }

        public double mse(double[] oczekiwaneWyjscia)
        {
            double mse = 0.0;

            for (int i = 0; i < warstwyINeurony[warstwyINeurony.Length - 1]; i++)
                mse += (oczekiwaneWyjscia[i] - wyjscia[warstwyINeurony.Length - 1][i]) * (oczekiwaneWyjscia[i] - wyjscia[warstwyINeurony.Length - 1][i]);

            //return mse / oczekiwaneWyjscia.Length;
            return mse / 2;
        }

        public double dajWyjscie(int ktore)
        {
            return wyjscia[warstwyINeurony.Length - 1][ktore];
        }

        public double[] dajWyjscia()
        {
            return wyjscia[warstwyINeurony.Length - 1];
        }
    }
}
